#ifndef PW_PBL_COROUTINE_RINGBUFFER_HPP
#define PW_PBL_COROUTINE_RINGBUFFER_HPP

#include <cstddef>
#include <concepts>
#include <optional>
#include "IntFor.hpp"
#include "RustMimic.hpp"

namespace pw_pbl::buffer
{

	template <typename DATA, std::size_t N>
	class RingBuffer
	{
	public:
		static constexpr auto ECapacity = NarrowIntFor<N>;
		using TSize = std::make_signed_t<std::remove_const_t<decltype(ECapacity)>>;

	protected:
		TSize _length;
		TSize _begin;
		MaybeUninit<std::array<DATA, N>> buf;

		static TSize wrapping_id(TSize id)
		{
			if constexpr (std::has_single_bit(ECapacity))
				return id & ECapacity - 1;
			else if (id >= ECapacity)
				return 0;
			else
				return id;
		}

	public:
		constexpr RingBuffer() : _length(0), _begin(0) {}
		constexpr ~RingBuffer()
		{
			if constexpr (!std::is_trivially_destructible_v<DATA>)
				while (!is_empty())
					unchecked_shift();
		}

	public:
		void clear()
		{
			if constexpr (!std::is_trivially_destructible_v<DATA>)
				while (!is_empty())
					unchecked_shift();
			else
				_length = _begin = 0;
		}
		constexpr TSize length() const { return _length; }
		constexpr bool is_empty() const { return _length == 0; }
		constexpr TSize remain() const { return ECapacity - _length; }
		constexpr bool is_full() const { return _length >= ECapacity; }

		void unchecked_push(DATA data)
		{
			auto id = wrapping_id(_begin + _length);
			buf.get()[id] = std::forward<DATA>(data);
			_length += 1;
		}
		DATA unchecked_shift()
		{
			auto data = std::forward<DATA>(buf.get()[_begin]);
			_length -= 1;
			_begin = wrapping_id(_begin + 1);
			return data;
		}

		std::optional<DATA> push(DATA data)
		{
			if (remain() == 0)
				return {std::forward<DATA>(data)};
			else
			{
				unchecked_push(std::forward<DATA>(data));
				return {};
			}
		}
		std::optional<DATA> shift()
		{
			if (is_empty())
				return {};
			else
				return unchecked_shift();
		}
	};
} // namespace pw_pbl::buffer

#endif // PW_PBL_COROUTINE_RINGBUFFER_HPP
